package edu.zju.udms.dnavigator.core.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Properties;
import java.util.UUID;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import edu.zju.udms.dnavigator.core.entity.ExtractorField;
import edu.zju.udms.dnavigator.core.entity.FieldTypeEnum;
import edu.zju.udms.dnavigator.core.extract.Extractor;

public class ConfigurationImpl extends Observable implements Configuration {
	
	private static final Logger logger=Logger.getLogger(ConfigurationImpl.class);
	private static final String propFile="conf/dnavigator.properties";
	private static final String extractorFile="conf/extractors.xml";
	
	//private String _baseDir;
	private Properties _properties=new Properties();
	private Properties _defaultProperties=new Properties();
	private List<Extractor> extractors=new ArrayList<>();
	
	public ConfigurationImpl() throws Exception {
		InputStream log4jInput = null;
		InputStream propertiesInput = null;
		InputStream in = null;
		
		/*log configure*/
		File tempFile=new File("conf/log4j.properties");
		if(tempFile.exists()){
			log4jInput=new FileInputStream(tempFile);
		}else{
			log4jInput=ConfigurationImpl.class.getResourceAsStream("/conf/log4j-default.properties");
		}
		PropertyConfigurator.configure(log4jInput);
		log4jInput.close();
		
		/*property configure*/
		propertiesInput=ConfigurationImpl.class.getResourceAsStream("/conf/dnavigator-default.properties");
		_defaultProperties.load(propertiesInput);
		propertiesInput.close();
		tempFile=new File(propFile);
		if(tempFile.exists()){
			propertiesInput=new FileInputStream(tempFile);
			_properties.load(propertiesInput);
			propertiesInput.close();
		}
		
		/*_baseDir=ConfigurationImpl.class.getResource("/").toString().substring(6);
		logger.info("Base目录:"+_baseDir);*/
		
		/*load extractors*/
		tempFile=new File(extractorFile);
		if(tempFile.exists()){
			in=new FileInputStream(tempFile);
			addExtractors(in);
			in.close();
		}
		logger.info("Configuration模块初始化完成");
	}
	
	private void parseMetaSource(Document doc) {
		/*TODO: 解析MetaSource.xml
		 * */
		Element dElement = doc.getDocumentElement();
		NodeList eElements = dElement.getElementsByTagName("extractor");
		for(int i=0;i<eElements.getLength();i++){  
			Element ee = (Element) eElements.item(i);
			String className = ee.getAttribute("class");
			Extractor extractor=null;
			Field[] fields;
			try {
				Class<?> clazz = Class.forName(className);
				extractor = (Extractor) clazz.newInstance();
				fields = clazz.getDeclaredFields();
			} catch (Exception e) {
				logger.error("class " + className + " not found", e);
				continue;
			}
			NodeList fieldNodes = ee.getElementsByTagName("field");
			for(int j=0; j<fieldNodes.getLength(); j++) {
				Element fieldNode = (Element) fieldNodes.item(j);
				String fname = fieldNode.getAttribute("name");
				String fvalue = fieldNode.getAttribute("value");
				if(fname != null){
					for(Field field:fields) {
						if(fname.equals(field.getName())){
							field.setAccessible(true);
							try {
								field.set(extractor, fvalue);
							} catch (Exception e) {
								logger.error("",e);
							}
							break;
						}
					}
				}
			}
			extractors.add(extractor);
		}
	}

	public String getProperty(String propertyName) {
		return _properties.containsKey(propertyName)?_properties.getProperty(propertyName):_defaultProperties.getProperty(propertyName);
	}

	public void setProperty(String propertyName, String propertyValue) {
		_properties.setProperty(propertyName, propertyValue);
		FileOutputStream output = null;
		try {
			File outputFile = new File(propFile);
			if(!outputFile.exists()){
				outputFile.createNewFile();
			}
			output = new FileOutputStream(outputFile);
			_properties.store(output, null);
		} catch (IOException e) {
			logger.error("",e);
		}finally{
			try {
				output.close();
			} catch (IOException e) {}
		}
	}

	@Override
	public List<Extractor> listExtractors() {
		return new ArrayList<>(extractors);
	}

	@Override
	public void addExtractors(InputStream in) {
		Document doc = null;
		try {
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder(); 
			doc = db.parse(in);
		}  catch (Exception e) {
			e.printStackTrace();
		}
		parseMetaSource(doc);
	}
	
	@Override
	public void addExtractors(String className, Map<String, String> words) throws Exception {
		Class<?> clazz = null;
		Extractor extractor = null;

		clazz = Class.forName(className);

		extractor = (Extractor) clazz.newInstance();
		Field[] fields = clazz.getDeclaredFields();
		for (Field f: fields) {
			f.setAccessible(true);
			if (words.containsKey(f.getName())) {
				f.set(extractor, words.get(f.getName()));
			}
		}

		
		if (extractor != null) {
			extractors.add(extractor);
		}
		
	}

	@Override
	public void deleteExtractor(UUID extractorId) throws Exception {
		for (Extractor e: extractors) {
			if (e.id.equals(extractorId)) {
				extractors.remove(e);
				return;
			}
		}
		throw new Exception("Extractor not Found");
	}


	@Override
	public void updateExtractor(UUID extractorId, Map<String, String> words) throws Exception {
		Extractor oldExtractor = null;
		for (Extractor e: extractors) {
			if (e.id.equals(extractorId)) {
				oldExtractor = e;
				break;
			}
		}
		
		Field[] fields = oldExtractor.getClass().getFields();
		for (Field f: fields) {
			if (words.containsKey(f.getName())) {
				f.setAccessible(true);
				f.set(oldExtractor, words.get(f.getName()));
			}
		}
	}
	
	@Override
	public void saveExtractors() throws Exception {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document doc = db.newDocument();
		Element root = doc.createElement("extractors");

		for (Extractor e: extractors) {
			Field[] fields = e.getClass().getDeclaredFields();
			Element exDom = doc.createElement("extractor");
			exDom.setAttribute("class", e.getClass().getName());
			
			for (Field f: fields) {
				ExtractorField[] attr = f.getAnnotationsByType(ExtractorField.class);
				for (int i = 0; i < attr.length; ++i) {
					if (attr[i].value().equals(FieldTypeEnum.SAVE)) {
						Element field = doc.createElement("field");
						field.setAttribute("name", f.getName());
						field.setAttribute("value", f.get(e).toString());
						exDom.appendChild(field);
					}
				}
			}
			root.appendChild(exDom);
		}
		doc.appendChild(root);
		
		TransformerFactory tf = TransformerFactory.newInstance();
		Transformer transformer = tf.newTransformer();
		DOMSource source = new DOMSource(doc);
		transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		PrintWriter pw = new PrintWriter(new FileOutputStream(extractorFile));
		StreamResult result = new StreamResult(pw);
		transformer.transform(source, result);
		logger.info("update extractor.xml successfully");
	}

}
